﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TowerDefense.Actors;

namespace TowerDefense
{
    public class BoundedGrid<E> : Actor,Grid<E>
    {
        private E[,] occupantArray; // the array storing the grid elements

        /**
     * Constructs an empty bounded grid with the given dimensions.
     * (Precondition: <code>rows > 0</code> and <code>cols > 0</code>.)
     * @param rows number of rows in BoundedGrid
     * @param cols number of columns in BoundedGrid
     */
        public BoundedGrid(int rows, int cols) : base(new Location(0,0))
        {
            if (rows <= 0)
                throw new Exception("rows <= 0");
            if (cols <= 0)
                throw new Exception("cols <= 0");
            occupantArray = new E[rows, cols];
        }

        public int getNumRows()
        {
            return occupantArray.GetLength(1);
        }

        public int getNumCols()
        {
            // Note: according to the constructor precondition, numRows() > 0, so
            // theGrid[0] is non-null.
            return occupantArray.GetLength(0);
        }

        public bool isValid(Location loc)
        {
            return 0 <= loc.getRow() && loc.getRow() < getNumRows()
                    && 0 <= loc.getCol() && loc.getCol() < getNumCols();
        }

        public List<Location> getOccupiedLocations()
        {
            List<Location> theLocations = new List<Location>();

            // Look at all grid locations.
            for (int r = 0; r < getNumRows(); r++)
            {
                for (int c = 0; c < getNumCols(); c++)
                {
                    // If there's an object at this location, put it in the array.
                    Location loc = new Location(r, c);
                    if (get(loc) != null)
                        theLocations.Add(loc);
                }
            }

            return theLocations;
        }

        public E get(Location loc)
        {
            if (!isValid(loc))
                throw new Exception("Location " + loc
                        + " is not valid");
            E o = occupantArray[loc.getRow(), loc.getCol()];
            //TODO: not sure if this upcast will handle the conversion to E
            return o;
        }

        /**
         * Puts a Generic type object at that grid location. If there is already something at that
         * position, it replaces it and returns what was previously there
         */
        public E put(Location loc, E obj)
        {
            if (!isValid(loc))
                throw new Exception("Location " + loc
                        + " is not valid");
            if (obj == null)
                throw new Exception("obj == null");

            // Add the object to the grid.
            E oldOccupant = get(loc);
            //Object o = obj; //TODO: not sure if this upcast will handle the conversion to E
            occupantArray[loc.getRow(), loc.getCol()] = obj;
            return oldOccupant;
        }

        public E remove(Location loc)
        {
            if (!isValid(loc))
                throw new Exception("Location " + loc
                        + " is not valid");

            // Remove the object from the grid.
            E r = get(loc);
            occupantArray[loc.getRow(), loc.getCol()] = default(E);
            return r;
        }

        public List<E> getNeighbors(Location loc)
        {
            List<E> neighbors = new List<E>();
            foreach (Location neighborLoc in getOccupiedAdjacentLocations(loc))
                neighbors.Add(get(neighborLoc));
            return neighbors;
        }

        public List<Location> getValidAdjacentLocations(Location loc)
        {
            List<Location> locs = new List<Location>();

            int d = Location.NORTH;
            for (int i = 0; i < Location.FULL_CIRCLE / Location.HALF_RIGHT; i++)
            {
                Location neighborLoc = loc.getAdjacentLocation(d);
                if (isValid(neighborLoc))
                    locs.Add(neighborLoc);
                d = d + Location.HALF_RIGHT;
            }
            return locs;
        }

        public List<Location> getEmptyAdjacentLocations(Location loc)
        {
            List<Location> locs = new List<Location>();
            foreach (Location neighborLoc in getValidAdjacentLocations(loc))
            {
                if (get(neighborLoc) == null)
                    locs.Add(neighborLoc);
            }
            return locs;
        }

        public List<Location> getOccupiedAdjacentLocations(Location loc)
        {
            List<Location> locs = new List<Location>();
            foreach (Location neighborLoc in getValidAdjacentLocations(loc))
            {
                if (get(neighborLoc) != null)
                    locs.Add(neighborLoc);
            }
            return locs;
        }

        /**
         * Creates a string that describes this grid.
         * @return a string with descriptions of all objects in this grid (not
         * necessarily in any particular order), in the format {loc=obj, loc=obj,
         * ...}
         */
        public String toString()
        {
            String s = "{";
            foreach (Location loc in getOccupiedLocations())
            {
                if (s.Length > 1)
                    s += ", ";
                s += loc + "=" + get(loc);
            }
            return s + "}";
        }
    }
}
